/**
 * \file main.cc
 * \brief Serial port driver kernel module.
 */

/*
   Copyright 2006 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <modinit.h>
#include <mutex.h>
#include <Log.h>
#include <Thread.h>
#include <Process.h>
#include <syscall.h>
#include <platform/Interrupt.h>

#include <stdio.h>
#include <string.h>
#include <i386/console.h>
#include <i386/io.h>
#include <i386/mem.h>
#include <errno.h>

const char *module_name = "serial";

#if 0
#define SERIAL_DEBUG printf
#else
#define SERIAL_DEBUG(fmt, ...) ;
#endif

#define BUF_SIZE 4096
#define NUM_PORTS 4

typedef struct {
	uint8 *buf;
	uint32 size, in_ptr, out_ptr;
	
	spinlock_t lock;
	semaphore_t cond;
} serial_queue_t;

typedef struct {
	// transmit and receive queues
	serial_queue_t rx, tx;
	
	// number of interrupts, receive interrupts, and transmit interrupts
	uint32 int_count, rx_count, tx_count;
	
	// number of framing errors, parity errors, and overrun errors
	uint32 ferr_count, perr_count, oerr_count;
	uint32 fifo_size;
	
	bool translate; // a \r after any \n found
	
	// hardware resources
	uint16 io_adr, irq;
	
	// bottom half tid
	tid_t tid;
	
	// bottom half condition
	spinlock_t cond;
	
	// device name in the form serial%d
	char name[10];
} serial_port_t;

/*
 * The available serial ports are enumerated at BIOS data address 0x400.
 */
static const uint16 *bios_ports = (uint16 *) 0x400;

/*
 * Number of serial ports found during initialization.
 */
static uint16 num_ports;

/*
 * Serial port information, including queues and resources
 */
static serial_port_t ports[NUM_PORTS];

static uint16 serial_id(uint16 io_adr);
static int serial_setup(serial_port_t *port, uint32 baud,
		uint32 bits, bool enable_fifo);

static bool interruptHandler(regs_t *);

static void queue_init(serial_queue_t &queue, uint32 size);

static int inq(serial_queue_t *q, uint8 data);
static int deq(serial_queue_t *q);

static void port_task(void *arg);
static void serviceTask(void *);

static void port_handler(serial_port_t *port);

static void serial_write(serial_port_t *port, uint8 *data, uint32 len);
static void serial_read(serial_port_t *port, uint8 *data, uint32 len);

static void test_task(void *arg);

#define MIN(a, b) ((a) < (b) ? (a) : (b))

int sd_printf(char *fmt, ...) {
	static char cbuf[500];
    va_list parms;
    int result;
    
	va_start(parms,fmt);
    result = vsprintf(cbuf,fmt,parms);
    va_end(parms);
    
    char *s = cbuf;
    while (*s != '\0') {
		serial_write(&ports[0], (uint8 *) s, 1);
		s++;
    }
	
    
    return result;
}

void sd_writeString(const char *str) {
	const char *s = str;
    while (*s != '\0') {
		serial_write(&ports[0], (uint8 *) s, 1);
		s++;
    }
}

extern "C"
void __init module_init(void) {
	// count serial ports
	uint16 count = 0;
	
	while (bios_ports[count] != 0 && count < NUM_PORTS) {
		SERIAL_DEBUG("BIOS data area reports port at %x\n", bios_ports[count]);
		
		sprintf(ports[count].name, "serial%d", count);
		
		queue_init(ports[count].rx, BUF_SIZE);
		queue_init(ports[count].tx, BUF_SIZE);
		
		ports[count].io_adr = bios_ports[count];
		
		if (ports[count].io_adr >> 8 == 2) {
			ports[count].irq = INT_BASE + 3;
		} else {
			ports[count].irq = INT_BASE + 4;
		}
		
		ports[count].fifo_size = serial_id(ports[count].io_adr);
		
		SERIAL_DEBUG("I/O=0x%04x, IRQ=%u, BUF_SIZE=%u\n", ports[count].io_adr,
			ports[count].irq - INT_BASE, BUF_SIZE);
		
		// set 115200 baud, 8N1, FIFO on
		if (serial_setup(&ports[count], 115200L, 8, 1) != 0) {
			SERIAL_DEBUG("Error setting bit rate for serial port %d\n", count);
			return;
		}
		
		count++;
	}
	
	// only set the serial port count if all ports are initialized
	num_ports = count;
	
	for (int i=0; i < num_ports; i++) {
		char tmp[20];
		sprintf(tmp, "serial%d bottom half", i);
		
		RefThread thread = Thread::createThread(port_task, &ports[i], tmp,
			Process::getSystemProcess());
		thread->run();
		
		ports[i].tid = thread->getTid();
	}

	/*for (uint32 i=0; i < num_ports; i++) {
		char tmp[20];
		sprintf(tmp, "serial%d service thread", i);
		
		taskManager.createTask(serviceTask, &ports[i], tmp, TASK_TYPE_KERNEL);
	}*/
	
	Log::setConsole(sd_writeString);
}

extern "C"
void __finalize module_finalize(void) {
}

/*
static void serviceTask(void *param) {
	serial_port_t *port = (serial_port_t *) param;
	
	RefMessageQueue mq;
	char *filename;
	int ret;
	
	uint8 *in;
	uint8 *out;
	rw_msg_head_t *request;
	rw_msg_head_t *resp;
	
	// current port settings
	uint32 baud = 115200L;
	uint32 bits = 8;
	bool enableFifo = true;
	
	RefProcess proc = processManager.getKernelProcess();
	
	qid_t qid = proc->createMessageQueue();
	if (qid < 0) {
		printf("Failed to create message queue because %d\n", qid);
		goto qid_error;
	}
	
	mq = proc->getMessageQueue(qid);
	
	filename = new char[30];
	sprintf(filename, "/dev/%s", port->name);
	
	ret = vfsManager.createFile(filename, mq);
	if (ret != 0) {
		printf("Failed to register message queue because %d\n", ret);
		goto file_error;
	}
	
	in = new uint8[MESSAGE_SIZE];
	out = new uint8[MESSAGE_SIZE];
	
	request = (rw_msg_head_t *) in;
	resp = (rw_msg_head_t *) out;
	
	while (true) {
		if (mq->receive(in, MESSAGE_SIZE) == -1) {
			goto error;
		}
		
		switch (request->type) {
			case _IO_READ: {
				resp->type = _IO_READ;
				resp->length = MIN(request->length, BUF_SIZE);
				
				serial_read(port, resp->data, resp->length);
				
				resp->code = 0;
				
				if (mq->reply(out,
					resp->length + sizeof(rw_msg_head_t)) == -1) {
					goto error;
				}
			}
			break;
			
			case _IO_WRITE: {
				uint32 len = MIN(request->length, BUF_SIZE);
				serial_write(port, request->data, len);
				
				resp->type = _IO_WRITE;
				resp->length = len;
				resp->code = 0;
				
				if (mq->reply(out, sizeof(rw_msg_head_t)) == -1) {
					goto error;
				}
			}
			break;
			
			case _IO_PARAM: {
				resp->type = _IO_PARAM;
				resp->length = 0;
				
				if (request->length == sizeof(int)) {
					int key = request->code;
					int val = *(int *) request->data;
					
					switch (key) {
						case _IO_PARAM_SET_BAUD:
							if (serial_setup(port, val, bits,
								enableFifo) != 0) {
								resp->code = -EINVAL;
							} else {
								baud = val;
								resp->code = 0;
							}
							break;
						
						case _IO_PARAM_GET_BAUD:
							resp->code = baud;
							break;
						
						case _IO_PARAM_SET_BITS:
							if (serial_setup(port, baud, val,
								enableFifo) != 0) {
								resp->code = -EINVAL;
							} else {
								bits = val;
								resp->code = 0;
							}
							break;
						
						case _IO_PARAM_GET_BITS:
							resp->code = bits;
							break;
						
						case _IO_PARAM_SET_XLATE:
							port->translate = val;
							resp->code = 0;
							break;
						
						case _IO_PARAM_GET_XLATE:
							resp->code = port->translate;
							break;
						
						default:
							resp->code = -EINVAL;
							break;
					}
				} else {
					resp->code = -EINVAL;
				}
							
				if (mq->reply(out, sizeof(rw_msg_head_t)) == -1) {
					goto error;
				}
			}
			break;
			
			default:
				resp->type = _IO_ERROR;
				resp->length = 0;
				resp->code = -ENOMSG;
				
				if (mq->reply(out, sizeof(rw_msg_head_t)) == -1) {
					goto error;
				}
				break;
		}
	}
	
	error:
	delete [] in;
	delete [] out;
	
	file_error:
	vfsManager.deleteFile(filename);
	delete [] filename;
	
	qid_error:
	proc->destroyMessageQueue(qid);
}
*/

/*
 * Returns the identity of the port type detected.
 */
static uint16 serial_id(uint16 io_adr) {
	uint32 i, j;

	/*
	 * set EFR = 0 (16650+ chips only). "The EFR can only be accessed after 
	 * writing [0xBF] to the LCR..." For 16550/A, this code zeroes the FCR 
	 * instead.
	 */
	outp(io_adr + 3, 0xBF);
	outp(io_adr + 2, 0);
	
	// set FCR = 1 to enable FIFOs (if any)
	outp(io_adr + 3, 0);
	outp(io_adr + 2, 0x01);
	
	// enabling FIFOs should set bits b7 and b6 in Interrupt ID register
	i = inp(io_adr + 2) & 0xC0;
	SERIAL_DEBUG("Serial chip type: ");
	
	// no FIFO -- check if scratch register exists
	if(i == 0) {
		outp(io_adr + 7, 0xA5);
		outp(0x80, 0xFF);		// prevent bus float returning 0xA5
		i = inp(io_adr + 7);
		
		outp(io_adr + 7, 0x5A);
		outp(0x80, 0xFF);		// prevent bus float returning 0x5A
		j = inp(io_adr + 7);
		
		// scratch register 7 exists
		if(i == 0xA5 && j == 0x5A) {
			SERIAL_DEBUG("8250A/16450 (no FIFO)\n");
		} else {
			// ALL 8250s (including 8250A) have serious problems...
			SERIAL_DEBUG("8250/8250B (no FIFO)\n");
		}
	} else if(i == 0x40) {
		SERIAL_DEBUG("UNKNOWN: assuming no FIFO\n");
	} else if(i == 0x80) {
		SERIAL_DEBUG("16550: defective FIFO disabled\n");
	} else if(i == 0xC0) {
		// for 16650+, should be able to read 0 from EFR else will read 1 from
		// FCR
		outp(io_adr + 3, 0xBF);
		if(inp(io_adr + 2) == 0) {
			SERIAL_DEBUG("16650+ (32-byte FIFO)\n");
			return 32;
		} else {
			SERIAL_DEBUG("16550A (16-byte FIFO)\n");
			return 16;
		}
	}
	
	return 1;
}

/*
 * Handles port setup.
 */
static int serial_setup(serial_port_t *port, uint32 baud, uint32 bits,
	bool enable_fifo) {
	SERIAL_DEBUG("Setting up port for %d baud, %dN1\n", baud, bits);
	
	uint32 divisor, io_adr, i;

	if (baud > 115200L || baud < 2) {
		SERIAL_DEBUG("Bit rate (%u) must be < 115200 and > 2\n", baud);
		return -1;
	}
	
	divisor = (uint32)(115200L / baud);
	
	if (bits < 7 || bits > 8) {
		SERIAL_DEBUG("Number of data bits (%u) must be 7 or 8\n", bits);
		return -1;
	}
	
	// set bit rate
	io_adr = port->io_adr;
	outp(io_adr + 3, 0x80);
	outp(io_adr + 0, divisor);
	
	divisor >>= 8;
	outp(io_adr + 1, divisor);
	
	// set number of data bits. This also sets no parity and 1 stop bit
	outp(io_adr + 3, (bits == 7) ? 2 : 3);
	
	// enable all interrupts EXCEPT transmit
	outp(io_adr + 1, 0x0D);
	
	// turn on FIFO, if any
	if (port->fifo_size > 1 && enable_fifo) {
		outp(io_adr + 2, 0xC7);
	} else {
		outp(io_adr + 2, 0);
	}
	
	// loopback off, interrupt gate (Out2) on, handshaking lines (RTS, DTR) off
	outp(io_adr + 4, 0x08);
	
	// clear dangling interrupts
	while(1) {
		i = inp(io_adr + 2);
		
		if(i & 0x01) {
			break;
		}
		
		(void)inp(io_adr + 0);
		(void)inp(io_adr + 5);
		(void)inp(io_adr + 6);
	}
	
	return 0;
}

static void port_task(void *arg) {
	serial_port_t *port = (serial_port_t *) arg;
	
	// init the condition lock before registering the handler, so we don't lose
	// an IRQ, if one occurs before we get to the loop
	port->cond = SPINLOCK_LOCKED;
	Interrupt::registerHandler(port->irq, interruptHandler);
	
	while (true) {
		// wait for an interrupt and avoid false wakes
		while (spinlock_try(&port->cond)) {
			syscall(SYSCALL_THREAD, SYSCALL_THREAD_WAIT);
		}
		
		port_handler(port);
	}
	
	Interrupt::unregisterHandler(port->irq, interruptHandler);
}

static void port_handler(serial_port_t *port) {
// count number of transmit and receive INTERRUPTS; not number of bytes
	char rx_int = 0, tx_int = 0;
	uint32 reason;
	int stat, c;
	
	port->int_count++;
	reason = inp(port->io_adr + 2);
	
	// CAREFUL: a loop inside an interrupt handler can cause the system to hang
	while ((reason & 0x01) == 0) {
		reason >>= 1;
		reason &= 0x07;
		
		// line status interrupt (highest priority) cleared by reading line
		// status register (LSR; register 5)
		if (reason == 3) {
			stat = inp(port->io_adr + 5);
			
			// 0x80 == one or more errors in Rx FIFO
   			// 0x10 == received BREAK
   			
   			// framing error
			if (stat & 0x08) {
				port->ferr_count++;
			}
			
			// parity error
			if (stat & 0x04) {
				port->perr_count++;
			}
			
			// overrun error
			if (stat & 0x02) {
				port->oerr_count++;
			}
		} else if (reason == 2) {
			// receive data interrupt (2nd highest priority) cleared by reading
			// receiver buffer register (register 0)
			
			// count ONE receive interrupt
			if (rx_int == 0) {
				port->rx_count++;
				rx_int = 1;
			}
			
			//spinlock_wait(&port->rx.lock);
			inq(&port->rx, inp(port->io_adr + 0));
			//spinlock_signal(&port->rx.lock);
			
			// notify the task waiting on the read buffer, if there is one
			mutex_signal(&port->rx.cond);
		} else if (reason == 6) {
			// character timeout interrupt (2nd highest priority; FIFO mode
			// only) cleared by receive buffer register
			
			// count ONE receive interrupt
			if (rx_int == 0) {
				port->rx_count++;
				rx_int = 1;
			}
			
			//spinlock_wait(&port->rx.lock);
			inq(&port->rx, inp(port->io_adr + 0));
			//spinlock_signal(&port->rx.lock);
			
			// notify the task waiting on the read buffer, if there is one
			mutex_signal(&port->rx.cond);
		} else if( reason == 1) {
			// transmit holding register empty interrupt (3rd highest priority)
			// cleared by reading the interrupt ID register (IIR; register 2)
			// or by writing into transmit holding register (THR; register 0)

			// queue up to port->fifo_size bytes
			for (int i = port->fifo_size; i != 0; i--) {
				//spinlock_wait(&port->tx.lock);
				c = deq(&port->tx);
				//spinlock_signal(&port->tx.lock);
				
				if (c == -1) {
					// empty transmit queue: disable further transmit
					// interrupts
					c = inp(port->io_adr + 1);
					
					if (c & 0x02) {
						outp(port->io_adr + 1, c & ~0x02);
					}
					
					break;
				}
				
				// count ONE transmit interrupt
				if (tx_int == 0) {
					port->tx_count++;
					tx_int = 1;
				}
				
				outp(port->io_adr + 0, c);
			}
			
			// notify the task waiting on the write buffer, if there is one
			mutex_signal(&port->tx.cond);
		} else if (reason == 0) {
			// modem status interrupt (4th highest priority) cleared by reading
			// the modem status register (MSR; register 6)
			
			inp(port->io_adr + 6);
		}
		
		reason = inp(port->io_adr + 2);
	}
}

static bool interruptHandler(regs_t *regs) {
	//static int count = 0;
	//printf_xy(71, 0, RED, "%08x", count++);
	
	bool ret = false;
	
	// wake any bottom halves on this IRQ
	for (int i=0; i < num_ports; i++) {
		if (ports[i].irq == regs->which_int) {
			spinlock_signal(&ports[i].cond);
			syscall(SYSCALL_THREAD, SYSCALL_THREAD_NOTIFY, ports[i].tid);
			
			ret = true;
		}
	}
	
	return ret;
}

/*
 * Read/Write serial port data.
 */
static void serial_read(serial_port_t *port, uint8 *data, uint32 len) {
	for (uint32 i=0 ; i < len; i++) {
		int res = deq(&port->rx);
		
		if (res == -1) {
			//mutex_wait(&port->rx.cond);
		} else {
			data[i] = (uint8) res;
		}
	}
}

static void serial_write(serial_port_t *port, uint8 *data, uint32 len) {
	bool kick = true;
	
	for (uint32 i=0; i < len; i++) {
		int res = -1;
		
		bool translate = data[i] == '\n' && port->translate;
		uint8 value = data[i];
		
		do {
			if (translate) {
				res = inq(&port->tx, '\r');
				
				if (res != -1) {
					translate = false;
				}
			}
			
			if (!translate) {
				res = inq(&port->tx, value);
			}
			
			if (res == -1) {
				// we don't need to kickstart if the fifo underflows
				//kick = false;
				//outp(port->io_adr + 1, inp(port->io_adr + 1) | 0x02);
				//mutex_wait(&port->tx.cond);
				break;
			}
		} while (res == -1);
	}
	
	if (kick) {
		outp(port->io_adr + 1, inp(port->io_adr + 1) | 0x02);
	}
}

static void queue_init(serial_queue_t &queue, uint32 size) {
	spinlock_init(&queue.lock);
	mutex_init(&queue.cond);
	
	queue.buf = new uint8[size];
	queue.size = size;
}

/*
 * Circular queue routines
 */

static int inq(serial_queue_t *q, uint8 data) {
	spinlock_wait(&q->lock);
	uint32 temp;

	temp = q->in_ptr + 1;
	if (temp >= q->size) {
		temp = 0;
	}
	
	// if in_ptr reaches out_ptr, the queue is full
	if (temp == q->out_ptr) {
		spinlock_signal(&q->lock);
		return -1;
	}
	
	q->buf[q->in_ptr] = data;
	q->in_ptr = temp;
	
	spinlock_signal(&q->lock);
	return 0;
}

static int deq(serial_queue_t *q) {
	spinlock_wait(&q->lock);
	int rv;

	// if out_ptr reaches in_ptr, the queue is empty
	if (q->out_ptr == q->in_ptr) {
		spinlock_signal(&q->lock);
		return -1;
	}
	
	rv = q->buf[q->out_ptr];
	q->out_ptr++;
	
	if (q->out_ptr >= q->size) {
		q->out_ptr = 0;
	}
	
	spinlock_signal(&q->lock);
	return rv;
}
